#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/wait.h>
#include <linux/poll.h>

#define DEV_SIZE	4096

struct c0_dev{
	unsigned int dev_major;
	struct class *devcls;
	struct device *dev;
	char *kvalue;
	wait_queue_head_t wqhead;
	struct fasync_struct *fasync;
};
struct c0_dev *c0;
bool have_data=false;


int c0_open(struct inode *inode,struct file *filp)
{
	struct c0_dev *dev=c0;
	int num=MINOR(inode->i_rdev);
	dev=&c0[num];
	filp->private_data=dev;
	return 0;
}
ssize_t c0_read(struct file *filp,char __user *buf,size_t count,loff_t *fpos)
{
	int ret = 0;
	unsigned long p = *fpos;
	unsigned int size = count;
	struct c0_dev *dev = filp->private_data;
	if(p>DEV_SIZE) return 0;
	if(size>DEV_SIZE-p) size=DEV_SIZE-p;
#if 1 //NOBLOCK
	printk("-------NONBLOCK-----\n");
	while(!have_data)
	{
		if (filp->f_flags & O_NONBLOCK) 
			return -EAGAIN;			
		wait_event_interruptible(c0->wqhead,have_data);
	}
#else //BLOCK
	printk("-------BLOCK--------\n");
	wait_event_interruptible(c0->wqhead,have_data);
#endif
	if(copy_to_user(buf,(void *)(dev->kvalue+p),size)!=0)
	{
		printk("copy_to_user failed\n");
		ret = -EFAULT;
	}
	else
	{
		*fpos += size;
		ret = size;
		printk("read %d byte(s) from %d\n",size,p);
	}
	have_data=false;
	return ret;
}
ssize_t c0_write(struct file *filp,const char __user *buf,size_t count,loff_t *fpos)
{
	int ret = 0;
	unsigned long p = *fpos;
	unsigned int size = count;
	struct c0_dev *dev = filp->private_data;
	
	if(p>DEV_SIZE) return 0;
	if(size>DEV_SIZE-p) size=DEV_SIZE-p;

	if(copy_from_user(dev->kvalue+p,buf,size)!= 0)
	{
		printk("copy_from_user failed\n");
		ret= -EFAULT;
	}
	else
	{
		*fpos += size;
		ret = size;
		printk("read %d byte(s) from %d\n",size,p);
	}
	have_data=true;
	wake_up_interruptible(&c0->wqhead);
	kill_fasync(&c0->fasync,SIGIO,POLLIN);
	return ret;
}
int c0_close(struct inode *inode,struct file *filp)
{
	printk("---------%s--------\n",__FUNCTION__);
	return 0;
}


int c0_fasync(int fd, struct file *filp, int on)
{
	return fasync_helper(fd,filp,on,&c0->fasync);
}

const struct file_operations my_fops={
	.open = c0_open,
	.read = c0_read,
	.write = c0_write,
	.release = c0_close,
	.fasync = c0_fasync,
};


static int __init chr_dev_init(void)
{
	int ret;
	c0=(struct c0_dev*)kmalloc(sizeof(struct c0_dev),GFP_KERNEL);
	if(IS_ERR(c0))
	{
		printk("kmalloc falied\n");
		ret = -ENODEV;
		goto err_0;
	}
	memset(c0,0,sizeof(struct c0_dev));
	c0->kvalue=kmalloc(DEV_SIZE,GFP_KERNEL);
	memset(c0->kvalue,0,DEV_SIZE);

	c0->dev_major=register_chrdev(0,"chr_dev_test",&my_fops);
	if(c0->dev_major<0)
	{
		printk("register failed\n");
		ret = -ENODEV;
		goto err_1;
	}
	c0->devcls = class_create(THIS_MODULE,"chr_cls");
	if(IS_ERR(c0->devcls))
	{
		printk("class create failed\n");
		ret = PTR_ERR(c0->devcls);
		goto err_2;
	}

	c0->dev = device_create(c0->devcls,NULL,MKDEV(c0->dev_major,0),NULL,"c0");
	if(IS_ERR(c0->dev))
	{
		printk("device create failed\n");
		ret = PTR_ERR(c0->dev);
		goto err_3;
	}
	init_waitqueue_head(&c0->wqhead);
	return 0;

err_3:
	device_destroy(c0->devcls,MKDEV(c0->dev_major,0));
err_2:
	class_destroy(c0->devcls);
err_1:
	unregister_chrdev(c0->dev_major,"chr_dev_test");
err_0:
	kfree(c0);
	return ret;
}

static void __exit chr_dev_exit(void)
{
	device_destroy(c0->devcls,MKDEV(c0->dev_major,0));
	class_destroy(c0->devcls);
	unregister_chrdev(c0->dev_major,"chr_dev_test");
	kfree(c0);
	printk("exit ok\n");
}

module_init(chr_dev_init);
module_exit(chr_dev_exit);
MODULE_LICENSE("GPL");

